#include <jni.h>
#include <android/native_window.h>
#include <android/nativewindowjni.h>
#include <media/NdkMediaCodec.h>
#define MAXBUFFERSIZE 100
ANativeWindow *window;
AMediaFormat *format;
AMediaCodec *codec;
int bufferIndex = -1;


typedef struct Frame {
	int index;
	uint8_t *data;
} Frame;


Frame frameBuffer[MAXBUFFERSIZE];
int frameCount = 0;


void releaseFrame(int index) {
	if (frameBuffer[index].data != NULL) {
		free(frameBuffer[index].data);
		frameBuffer[index].data = NULL;
	} 
}

JNIEXPORT jint JNICALL Javacomexample_ndkcodecMainActivityinitDecoder(JNIEnv *env, jobject thiz, jstring mime_type, jint width, jint height, jobject surface) {
	const char *mime = env->GetStringUTFChars(mimetype, 0);
	window = ANativeWindowfromSurface(env, surface);
	format = AMediaFormatnew();
	AMediaFormatsetString(format, AMEDIAFORMATKEYMIME, mime);
	AMediaFormatsetInt32(format, AMEDIAFORMATKEY_WIDTH, width);
	AMediaFormatsetInt32(format, AMEDIAFORMATKEYHEIGHT, height);
	codec = AMediaCodeccreateDecoderByType(mime);
	mediastatust status = AMediaCodecconfigure(codec, format, window, NULL, 0);
	env->ReleaseStringUTFChars(mimetype, mime);
	return (jint) status;
}

JNIEXPORT jint JNICALL Javacomexample_ndkcodecMainActivitystartDecoder(JNIEnv *env, jobject thiz) {
	return (jint) AMediaCodec_start(codec);
}

JNIEXPORT jint JNICALL Javacomexample_ndkcodecMainActivitystopDecoder(JNIEnv *env, jobject thiz) {
	return (jint) AMediaCodec_stop(codec);
}

JNIEXPORT jint JNICALL Javacomexample_ndkcodecMainActivityreleaseDecoder(JNIEnv *env, jobject thiz) {
	int i;
	for (i = 0;i < MAXBUFFERSIZE;i++) {
		releaseFrame(i);
	} 
	return (jint) AMediaCodec_delete(codec);
}

JNIEXPORT jint JNICALL Javacomexample_ndkcodecMainActivitydecodeFrame(JNIEnv *env, jobject thiz, jint index) {
	if (bufferIndex >= 0) {
		AMediaCodecreleaseOutputBuffer(codec, bufferIndex, false);
		bufferIndex = -1;
	}
	ssizet status = AMediaCodec_dequeueOutputBuffer(codec, &bufferIndex, 0);
	if (status >= 0 && bufferIndex >= 0) {
		AMediaCodecBufferInfo info;
		AMediaCodecgetOutputBufferInfo(codec, bufferIndex, &info);
		int size = info.size;
		uint8t *data = (uint8_t *) malloc(size);
		AMediaCodecgetOutputBufferData(codec, bufferIndex, &data);
		AMediaCodecreleaseOutputBuffer(codec, bufferIndex, false);
		bufferIndex = -1;
		Frame frame = {
			index, data};
		memcpy(&frameBuffer[frameCount % MAXBUFFERSIZE], &frame, sizeof(Frame));
		releaseFrame((frameCount - MAXBUFFERSIZE + 1) % MAXBUFFERSIZE);
		frameCount++;
		return size;
	} 
	if (status == AMEDIACODECINFOOUTPUTFORMATCHANGED) {
		AMediaFormat *outputFormat = NULL;
		outputFormat = AMediaCodecgetOutputFormat(codec);
		ANativeWindowsetBuffersGeometry(window, AMediaFormatgetInt32(outputFormat, AMEDIAFORMATKEYWIDTH), AMediaFormatgetInt32(outputFormat, AMEDIAFORMATKEYHEIGHT), WINDOWFORMATRGBA8888);
		AMediaFormatdelete(outputFormat);
	} 
	return -1;
}

JNIEXPORT jint JNICALL Javacomexample_ndkcodecMainActivityrequestFrame(JNIEnv *env, jobject thiz, jint index, jbyteArray data) {
	int i;
	for (i = 0;i < MAXBUFFERSIZE;i++) {
		Frame frame = frameBuffer[i];
		if (frame.index == index) {
			env->SetByteArrayRegion(data, 0, sizeof(frame.data), (jbyte *) frame.data);
			releaseFrame(i);
			return sizeof(frame.data);
		} 
	}
	return -1;
}

